﻿using System.Collections.Generic;
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Reflection;
using System.Reflection.Emit;
using UnityEngine;
using UnityEditor;
using UnityEngine.Serialization;
using XLua;
using Object = UnityEngine.Object;
using SerializedTypeInfo = LuaBehaviour.SerializedTypeInfo;

[CustomEditor(typeof(LuaBehaviour), true)]
[CanEditMultipleObjects]
public class LuaBehaviourEditor : Editor
{
    static Dictionary<Type, Type> objWrapEditorDict = new Dictionary<Type, Type>();
    static ModuleBuilder editorModule;
    static LuaBehaviourEditor()
    {
        AppDomain myDomain = Thread.GetDomain();
        AssemblyName myAsmName = new AssemblyName();
        myAsmName.Name = "LuaBehaviourEditor";
        AssemblyBuilder myAsmBuilder = AssemblyBuilder.DefineDynamicAssembly(myAsmName,AssemblyBuilderAccess.RunAndCollect);
        editorModule = myAsmBuilder.DefineDynamicModule("LuaBehaviourEditorModule");
    }

    static Type GetWrapType(Type objType)
    {
        if (objWrapEditorDict.ContainsKey(objType))
        {
            return objWrapEditorDict[objType];
        }
        TypeBuilder wrapTypeBld = editorModule.DefineType("wrap" + objType.FullName, TypeAttributes.Public, typeof(ScriptableObject));
        FieldBuilder objField = wrapTypeBld.DefineField("obj", objType, FieldAttributes.Public);
        Type wrapType = wrapTypeBld.CreateType();
        objWrapEditorDict.Add(objType, wrapType);
        return wrapType;
    }

    Object _luaAsset;
    private SerializedProperty m_LuaScriptPath;
    private LuaBehaviour mLuaBehaviour;
    private List<SerializedTypeInfo> infoList;
    private long lastWriteTime = 0;

    protected void OnEnable()
    {
        m_LuaScriptPath = serializedObject.FindProperty("luaScriptPath");
        mLuaBehaviour = target as LuaBehaviour;
        lastWriteTime = 0;
        infoList = new List<SerializedTypeInfo>();
        LuaBehaviour.InitLoaders(null);
        OnLoadLuaAsset(m_LuaScriptPath.stringValue);
    }

    void OnLoadLuaAsset(string lua)
    {
        if (string.IsNullOrEmpty(lua))
            return;
        string path;
        if (LuaSearchPathFinder.IsFindLuaModule(lua, out path))
        {
            var asss = AssetDatabase.FindAssets(lua.Split('/', '\\')[^1]);
            var paths = asss.Select(aid => AssetDatabase.GUIDToAssetPath(aid)).ToList();
            string that = paths.ToList().Find(f => f.EndsWith(lua) || f.EndsWith(lua + ".lua"));
            if (!string.IsNullOrEmpty(that))
                _luaAsset = AssetDatabase.LoadAssetAtPath<DefaultAsset>(that);

            if (_luaAsset == null)
                _luaAsset = new Object();
            if (lua != m_LuaScriptPath.stringValue)
                OnLuaScriptPathChanged(lua);
            OnLuaPropertyUpdate(path);
        }
    }
    //如果Lua文件改变了，则需要重新初始化一遍需要序列化的信息。同时保留已经序列化的数据。
    private void OnLuaAssetChanged(Object newLuaAsset)
    {
            var path = AssetDatabase.GetAssetPath(newLuaAsset);
            path = Path.GetFullPath(path);
            if(path.EndsWith(".lua") && LuaSearchPathFinder.IsFindLuaPath(path,out string lua))
            {
                _luaAsset = newLuaAsset;
                OnLuaScriptPathChanged(lua);
                OnLuaPropertyUpdate(path);
            }
    }

    private void OnLuaScriptPathChanged(string lua)
    {
        m_LuaScriptPath.stringValue = lua.Replace(".lua","").Replace('\\','/');
        serializedObject.ApplyModifiedProperties();
        lastWriteTime = 0;
        infoList = new List<SerializedTypeInfo>();
        mLuaBehaviour.references = new List<LuaBehaviour.SerializedReference>();
        mLuaBehaviour.values = new List<LuaBehaviour.SerializedValue>();
        serializedObject.Update();
    }

    void OnLuaPropertyUpdate(string path){
        long curTime = File.GetLastWriteTime(path).Ticks;
        if (curTime != lastWriteTime)
        {
            lastWriteTime = curTime;
            infoList = GetInfoList();
            var preObjValues = mLuaBehaviour.references;
            var preValues = mLuaBehaviour.values;
            mLuaBehaviour.references = new List<LuaBehaviour.SerializedReference>();
            mLuaBehaviour.values = new List<LuaBehaviour.SerializedValue>();
            foreach (var info in infoList)
            {
                if(info.ValueType.IsSubclassOf(typeof(UnityEngine.Object)))
                {
                    mLuaBehaviour.references.Add(
                        new LuaBehaviour.SerializedReference() {
                            key = info.ValueName,
                            obj = GetObjValueInLuaBehavior(info, preObjValues)
                        });
                }
                else
                {
                    mLuaBehaviour.values.Add(
                        new LuaBehaviour.SerializedValue() {
                            key = info.ValueName,
                            json = GetValueInLuaBehavior(info, preValues)
                        });
                }
            }
        }
    }

    private UnityEngine.Object GetObjValueInLuaBehavior(SerializedTypeInfo info, List<LuaBehaviour.SerializedReference> values)
    {
        foreach(var value in values)
        {
            if(value.key == info.ValueName)
            {
                return value.obj;
            }
        }
        return null;
    }

    private string GetValueInLuaBehavior(SerializedTypeInfo info, List<LuaBehaviour.SerializedValue> values)
    {
        foreach (var value in values)
        {
            if (value.key == info.ValueName)
            {
                var obj1 = LuaBehaviour.JsonToValue(value.json, info.ValueType);
                return LuaBehaviour.ValueToJson(obj1);
            }
        }
        Type wrapType = typeof(LuaBehaviour.ObjWrap<>).MakeGenericType(info.ValueType);
        var target = Activator.CreateInstance(wrapType);
        return JsonUtility.ToJson(target);
    }

    //运行时和编辑时获取的方式不一样，运行时直接从实例中取即可，编辑时需要起一个虚拟机来加载序列化信息。
    private List<SerializedTypeInfo> GetInfoList()
    {      
        List<SerializedTypeInfo> infoList = new List<SerializedTypeInfo>();
        LuaEnv luaEnv = null;
        LuaTable luaClass = null;
        if (!Application.isPlaying)
        {
            luaEnv = new LuaEnv();
            LuaBehaviour.InitLoaders(luaEnv);
            var rets = luaEnv.DoString($"return require \"{m_LuaScriptPath.stringValue}\"");
            luaClass = (LuaTable)rets[0];
        }
        else
        {
            var rets = LuaBehaviour.LuaEnvInstance.DoString($"return require \"{m_LuaScriptPath.stringValue}\"");
            luaClass = (LuaTable)rets[0];
        }
        return mLuaBehaviour.GetSerializedTypeInfoList(luaClass);
    }
    
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        serializedObject.Update();
        //绘制Lua路径
        var newLuaAsset = EditorGUILayout.ObjectField("Lua Asset", _luaAsset, typeof(DefaultAsset), false);
        if (newLuaAsset != _luaAsset && newLuaAsset != null)
            OnLuaAssetChanged(newLuaAsset);
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.PrefixLabel("Lua Path");
        var newScript = EditorGUILayout.TextArea(m_LuaScriptPath.stringValue);
        if (newScript != m_LuaScriptPath.stringValue)
        {
            OnLoadLuaAsset(newScript);
        }
        EditorGUILayout.EndHorizontal();
        EditorGUILayout.Space(5);
        //绘制所有需要注入的对象
        foreach (var info in infoList)
        {
            if (info.ValueType.IsSubclassOf(typeof(UnityEngine.Object)))
            {
                DrawObjValueView(info);
            }
            else
            {
                DrawValueView(info);
            }
        }
        serializedObject.ApplyModifiedProperties();
    }

    //UnityEngine.Object都一个样，所以直接绘制即可
    private void DrawObjValueView(SerializedTypeInfo info)
    {
        for (int i = 0; i < mLuaBehaviour.references.Count; i++)
        {
            if(info.ValueName == mLuaBehaviour.references[i].key)
            {
                mLuaBehaviour.references[i].obj = EditorGUILayout.ObjectField(
                        mLuaBehaviour.references[i].key,
                        mLuaBehaviour.references[i].obj,
                        info.ValueType,
                        true
                    );
            }
        }
    }

    //感觉分类型去绘制有点麻烦，所以用ScriptableObject包装一下，通过SerializedObject让unity自己绘制。
    private void DrawValueView(SerializedTypeInfo info)
    {
        for(int i = 0; i < mLuaBehaviour.values.Count; i++) { 
            if (info.ValueName == mLuaBehaviour.values[i].key)
            {
                var editorType = GetWrapType(info.ValueType);
                var objField = editorType.GetField("obj");
                var target = CreateInstance(editorType);
                var value = LuaBehaviour.JsonToValue(mLuaBehaviour.values[i].json, info.ValueType);
                objField.SetValue(target, value);
                var so = new SerializedObject(target);
                EditorGUILayout.PropertyField(so.FindProperty("obj"), new GUIContent(info.ValueName));
                so.ApplyModifiedProperties();
                mLuaBehaviour.values[i].json = LuaBehaviour.ValueToJson(objField.GetValue(target));
            }
        }
    }
}
